import { isNull, isUndefined, PagedList, PageParams, StringIndexer } from "mmb";
import * as productpb from "../grpc-client/service/product_pb";
import {
  getBgsProductOpSafely,
  getMetadata,
  getOptionsOfLight,
  wrapperGrpcError,
} from "../grpc-client/grpcAdapter";
import DataSource from "../bg-enums/dataSource";
import { SyncAction } from "../bg-enums/sync";
import {
  ProductAttributeDef,
  Product,
  ProductSpec,
  CreateProductParams,
  UpdateProductParams,
} from "./declareTypes";
import { jsTimestampToPb } from "./helper";

/****************************************************************************************/
/*                                   product entity                                     */
/****************************************************************************************/

const productPbToObject = (pb: productpb.ProductEntity): Product => {
  return {
    productId: `${pb.getId()}`,
    brandId: `${pb.getBrandId()}`,
    name: pb.getName(),
    pics: pb.getPicsList(),
    intro: pb.getIntro(),
    introPics: pb.getIntroPicsList(),
    hasSpecs: pb.getHasSpecs(),
    priceMin: pb.getPriceMin(),
    priceMax: pb.getPriceMax(),
    productGroupId:
      pb.getProductGroupId() === 0 ? undefined : `${pb.getProductGroupId()}`,
    remotePK: `${pb.getRemotePk()}`,
    productAttributeDefs: pb
      .getProductAttributeDefsList()
      .map(productAttributeDefPbToObject),
    productSpecs: pb.getProductSpecsList().map(productSpecPbToObject),
  };
};

const productAttributeDefPbToObject = (
  pb: productpb.ProductAttributeDefEntity
): ProductAttributeDef => {
  return {
    key: pb.getKey(),
    values: pb.getValuesList(),
  };
};

const productAttributeDefObjectToPb = (
  o: ProductAttributeDef
): productpb.ProductAttributeDefEntity => {
  const pb = new productpb.ProductAttributeDefEntity();
  pb.setKey(o.key);
  pb.setValuesList(o.values);
  return pb;
};

const productSpecPbToObject = (
  pb: productpb.ProductSpecEntity
): ProductSpec => {
  return {
    productSpecId: `${pb.getId()}`,
    productAttributes: JSON.parse(pb.getProductAttributes()),
    name: pb.getName(),
    price: pb.getPrice(),
  };
};

const productSpecObjectToPb = (o: ProductSpec) => {
  const pb = new productpb.ProductSpecEntity();
  pb.setProductAttributes(JSON.stringify(o.productAttributes));
  pb.setName(o.productAttributes.map((v) => v.value).join("/"));
  pb.setPrice(o.price);
  return pb;
};

/****************************************************************************************/
/*                                   product cmd                                        */
/****************************************************************************************/

export const createProduct = async ({
  brandId,
  name,
  price,
  pics,
  intro,
  introPics,
  productGroupId,
  productAttributeDefs,
  productSpecs,
}: CreateProductParams): Promise<Product> => {
  const cl = getBgsProductOpSafely();
  const req = new productpb.CreateProductRequest();

  req.setBrandId(Number(brandId));
  req.setName(name);
  if (productSpecs.length === 0) {
    if (!isUndefined(price)) {
      req.setPrice(price);
    }
  }
  req.setPicsList(pics);
  if (!isUndefined(intro)) {
    req.setIntro(intro);
  }
  req.setIntroPicsList(introPics);
  if (!isUndefined(productGroupId)) {
    req.setProductGroupId(Number(productGroupId));
  }
  req.setProductAttributeDefsList(
    productAttributeDefs.map(productAttributeDefObjectToPb)
  );
  req.setProductSpecsList(productSpecs.map(productSpecObjectToPb));

  return new Promise((resolve, reject) => {
    cl.createProduct(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      resolve(productPbToObject(res.getProduct()!));
    });
  });
};

export const updateProduct = async ({
  productId,
  brandId,
  name,
  price,
  pics,
  intro,
  introPics,
  productGroupId,
  productAttributeDefs,
  productSpecs,
}: UpdateProductParams): Promise<Product> => {
  const cl = getBgsProductOpSafely();
  const req = new productpb.UpdateProductRequest();

  req.setId(Number(productId));
  req.setBrandId(Number(brandId));
  req.setName(name);
  if (productSpecs.length === 0) {
    if (!isUndefined(price)) {
      req.setPrice(price);
    }
  }
  req.setPicsList(pics);
  if (!isUndefined(intro)) {
    req.setIntro(intro);
  }
  req.setIntroPicsList(introPics);
  if (!isUndefined(productGroupId)) {
    req.setProductGroupId(Number(productGroupId));
  }
  req.setProductAttributeDefsList(
    productAttributeDefs.map(productAttributeDefObjectToPb)
  );
  req.setProductSpecsList(productSpecs.map(productSpecObjectToPb));

  return new Promise((resolve, reject) => {
    cl.updateProduct(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      resolve(productPbToObject(res.getProduct()!));
    });
  });
};

export interface RemoveProductParams {
  productId: string;
}

export const removeProduct = async ({
  productId,
}: RemoveProductParams): Promise<Product> => {
  const cl = getBgsProductOpSafely();
  const req = new productpb.RemoveProductRequest();

  req.setId(Number(productId));

  return new Promise((resolve, reject) => {
    cl.removeProduct(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      resolve(productPbToObject(res.getProduct()!));
    });
  });
};

/****************************************************************************************/
/*                                   product query                                      */
/****************************************************************************************/

export const getProduct = async (productId: string): Promise<Product> => {
  const cl = getBgsProductOpSafely();
  const req = new productpb.GetProductRequest();

  req.setId(Number(productId));

  return new Promise((resolve, reject) => {
    cl.getProduct(req, getMetadata(), getOptionsOfLight(), (err, res) => {
      if (!isNull(err)) {
        reject(wrapperGrpcError(err.details));
        return;
      }
      const opRet = res.getOpRet()!;
      if (!opRet.getSuccess()) {
        reject(wrapperGrpcError(opRet.getMsg()));
        return;
      }
      resolve(productPbToObject(res.getProduct()!));
    });
  });
};

export interface listNormalProductsByBrandIDParam {
  brandId: string;
  productGroupId?: string;
  keyword?: string;
}

export const listNormalProductsByBrandID = async (
  { brandId, productGroupId, keyword }: listNormalProductsByBrandIDParam,
  pager: PageParams
): Promise<PagedList<Product>> => {
  const cl = getBgsProductOpSafely();
  const req = new productpb.ListNormalProductsByBrandIDRequest();

  req.setLimit(pager.limit);
  req.setOffset(pager.offset);
  req.setTs(jsTimestampToPb(pager.ts));
  req.setBrandId(Number(brandId));
  if (!isUndefined(productGroupId)) {
    req.setProductGroupId(Number(productGroupId));
  }
  if (!isUndefined(keyword)) {
    req.setKeyword(keyword);
  }

  return new Promise((resolve, reject) => {
    cl.listNormalProductsByBrandID(
      req,
      getMetadata(),
      getOptionsOfLight(),
      (err, res) => {
        if (!isNull(err)) {
          reject(wrapperGrpcError(err.details));
          return;
        }
        const opRet = res.getOpRet()!;
        if (!opRet.getSuccess()) {
          reject(wrapperGrpcError(opRet.getMsg()));
          return;
        }
        const ret: PagedList<Product> = {
          total: 0,
          items: [],
        };
        ret.total = res.getTotal();
        ret.items = res.getProductsList().map((u) => productPbToObject(u));
        resolve(ret);
      }
    );
  });
};
